/*
 * Copyright (C) 2012-2017 ARM Limited. All rights reserved.
 * 
 * This program is free software and is provided to you under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence.
 * 
 * A copy of the licence is included with the program, and can also be obtained from Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "mali_executor.h"
#include "mali_scheduler.h"
#include "mali_kernel_common.h"
#include "mali_kernel_core.h"
#include "mali_osk.h"
#include "mali_osk_list.h"
#include "mali_pp.h"
#include "mali_pp_job.h"
#include "mali_group.h"
#include "mali_pm.h"
#include "mali_timeline.h"
#include "mali_osk_profiling.h"
#include "mali_session.h"
#include "mali_osk_mali.h"

/*
 * If dma_buf with map on demand is used, we defer job deletion and job queue
 * if in atomic context, since both might sleep.
 */
#if defined(CONFIG_DMA_SHARED_BUFFER) && !defined(CONFIG_MALI_DMA_BUF_MAP_ON_ATTACH)
#define MALI_EXECUTOR_USE_DEFERRED_PP_JOB_DELETE 1
#define MALI_EXECUTOR_USE_DEFERRED_PP_JOB_QUEUE 1
#endif /* !defined(CONFIG_DMA_SHARED_BUFFER) && !defined(CONFIG_MALI_DMA_BUF_MAP_ON_ATTACH) */

/*
 * ---------- static type definitions (structs, enums, etc) ----------
 */

enum mali_executor_state_t {
	EXEC_STATE_NOT_PRESENT, /* Virtual group on Mali-300/400 (do not use) */
	EXEC_STATE_DISABLED,    /* Disabled by core scaling (do not use) */
	EXEC_STATE_EMPTY,       /* No child groups for virtual group (do not use) */
	EXEC_STATE_INACTIVE,    /* Can be used, but must be activate first */
	EXEC_STATE_IDLE,        /* Active and ready to be used */
	EXEC_STATE_WORKING,     /* Executing a job */
};

/*
 * ---------- global variables (exported due to inline functions) ----------
 */

/* Lock for this module (protecting all HW access except L2 caches) */
_mali_osk_spinlock_irq_t *mali_executor_lock_obj = NULL;

mali_bool mali_executor_hints[MALI_EXECUTOR_HINT_MAX];

/*
 * ---------- static variables ----------
 */

/* Used to defer job scheduling */
static _mali_osk_wq_work_t *executor_wq_high_pri = NULL;

/* Store version from GP and PP (user space wants to know this) */
static u32 pp_version = 0;
static u32 gp_version = 0;

/* List of physical PP groups which are disabled by some external source */
static _MALI_OSK_LIST_HEAD_STATIC_INIT(group_list_disabled);
static u32 group_list_disabled_count = 0;

/* List of groups which can be used, but activate first */
static _MALI_OSK_LIST_HEAD_STATIC_INIT(group_list_inactive);
static u32 group_list_inactive_count = 0;

/* List of groups which are active and ready to be used */
static _MALI_OSK_LIST_HEAD_STATIC_INIT(group_list_idle);
static u32 group_list_idle_count = 0;

/* List of groups which are executing a job */
static _MALI_OSK_LIST_HEAD_STATIC_INIT(group_list_working);
static u32 group_list_working_count = 0;

/* Virtual group (if any) */
static struct mali_group *virtual_group = NULL;

/* Virtual group state is tracked with a state variable instead of 4 lists */
static enum mali_executor_state_t virtual_group_state = EXEC_STATE_NOT_PRESENT;

/* GP group */
static struct mali_group *gp_group = NULL;

/* GP group state is tracked with a state variable instead of 4 lists */
static enum mali_executor_state_t gp_group_state = EXEC_STATE_NOT_PRESENT;

static u32 gp_returned_cookie = 0;

/* Total number of physical PP cores present */
static u32 num_physical_pp_cores_total = 0;

/* Number of physical cores which are enabled */
static u32 num_physical_pp_cores_enabled = 0;

/* Enable or disable core scaling */
static mali_bool core_scaling_enabled = MALI_TRUE;

/* Variables to allow safe pausing of the scheduler */
static _mali_osk_wait_queue_t *executor_working_wait_queue = NULL;
static u32 pause_count = 0;

/* PP cores haven't been enabled because of some pp cores haven't been disabled. */
static int core_scaling_delay_up_mask[MALI_MAX_NUMBER_OF_DOMAINS] = { 0 };

/* Variables used to implement notify pp core changes to userspace when core scaling
 * is finished in mali_executor_complete_group() function. */
static _mali_osk_wq_work_t *executor_wq_notify_core_change = NULL;
static _mali_osk_wait_queue_t *executor_notify_core_change_wait_queue = NULL;

/*
 * ---------- Forward declaration of static functions ----------
 */
static mali_bool mali_executor_is_suspended(void *data);
static mali_bool mali_executor_is_working(void);
static void mali_executor_disable_empty_virtual(void);
static mali_bool mali_executor_physical_rejoin_virtual(struct mali_group *group);
static mali_bool mali_executor_has_virtual_group(void);
static mali_bool mali_executor_virtual_group_is_usable(void);
static void mali_executor_schedule(void);
static void mali_executor_wq_schedule(void *arg);
static void mali_executor_send_gp_oom_to_user(struct mali_gp_job *job);
static void mali_executor_complete_group(struct mali_group *group,
		mali_bool success,
		struct mali_gp_job **gp_job_done,
		struct mali_pp_job **pp_job_done);
static void mali_executor_change_state_pp_physical(struct mali_group *group,
		_mali_osk_list_t *old_list,
		u32 *old_count,
		_mali_osk_list_t *new_list,
		u32 *new_count);
static mali_bool mali_executor_group_is_in_state(struct mali_group *group,
		enum mali_executor_state_t state);

static void mali_executor_group_enable_internal(struct mali_group *group);
static void mali_executor_group_disable_internal(struct mali_group *group);
static void mali_executor_core_scale(unsigned int target_core_nr);
static void mali_executor_core_scale_in_group_complete(struct mali_group *group);
static void mali_executor_notify_core_change(u32 num_cores);
static void mali_executor_wq_notify_core_change(void *arg);
static void mali_executor_change_group_status_disabled(struct mali_group *group);
static mali_bool mali_executor_deactivate_list_idle(mali_bool deactivate_idle_group);
static void mali_executor_set_state_pp_physical(struct mali_group *group,
		_mali_osk_list_t *new_list,
		u32 *new_count);

/*
 * ---------- Actual implementation ----------
 */

_mali_osk_errcode_t mali_executor_initialize(void)
{
	mali_executor_lock_obj = _mali_osk_spinlock_irq_init(_MALI_OSK_LOCKFLAG_ORDERED, _MALI_OSK_LOCK_ORDER_EXECUTOR);
	if (NULL == mali_executor_lock_obj) {
		mali_executor_terminate();
		return _MALI_OSK_ERR_NOMEM;
	}

	executor_wq_high_pri = _mali_osk_wq_create_work_high_pri(mali_executor_wq_schedule, NULL);
	if (NULL == executor_wq_high_pri) {
		mali_executor_terminate();
		return _MALI_OSK_ERR_NOMEM;
	}

	executor_working_wait_queue = _mali_osk_wait_queue_init();
	if (NULL == executor_working_wait_queue) {
		mali_executor_terminate();
		return _MALI_OSK_ERR_NOMEM;
	}

	executor_wq_notify_core_change = _mali_osk_wq_create_work(mali_executor_wq_notify_core_change, NULL);
	if (NULL == executor_wq_notify_core_change) {
		mali_executor_terminate();
		return _MALI_OSK_ERR_NOMEM;
	}

	executor_notify_core_change_wait_queue = _mali_osk_wait_queue_init();
	if (NULL == executor_notify_core_change_wait_queue) {
		mali_executor_terminate();
		return _MALI_OSK_ERR_NOMEM;
	}

	return _MALI_OSK_ERR_OK;
}

void mali_executor_terminate(void)
{
	if (NULL != executor_notify_core_change_wait_queue) {
		_mali_osk_wait_queue_term(executor_notify_core_change_wait_queue);
		executor_notify_core_change_wait_queue = NULL;
	}

	if (NULL != executor_wq_notify_core_change) {
		_mali_osk_wq_delete_work(executor_wq_notify_core_change);
		executor_wq_notify_core_change = NULL;
	}

	if (NULL != executor_working_wait_queue) {
		_mali_osk_wait_queue_term(executor_working_wait_queue);
		executor_working_wait_queue = NULL;
	}

	if (NULL != executor_wq_high_pri) {
		_mali_osk_wq_delete_work(executor_wq_high_pri);
		executor_wq_high_pri = NULL;
	}

	if (NULL != mali_executor_lock_obj) {
		_mali_osk_spinlock_irq_term(mali_executor_lock_obj);
		mali_executor_lock_obj = NULL;
	}
}

void mali_executor_populate(void)
{
	u32 num_groups;
	u32 i;

	num_groups = mali_group_get_glob_num_groups();

	/* Do we have a virtual group? */
	for (i = 0; i < num_groups; i++) {
		struct mali_group *group = mali_group_get_glob_group(i);

		if (mali_group_is_virtual(group)) {
			virtual_group = group;
			virtual_group_state = EXEC_STATE_INACTIVE;
			break;
		}
	}

	/* Find all the available physical GP and PP cores */
	for (i = 0; i < num_groups; i++) {
		struct mali_group *group = mali_group_get_glob_group(i);

		if (NULL != group) {
			struct mali_pp_core *pp_core = mali_group_get_pp_core(group);
			struct mali_gp_core *gp_core = mali_group_get_gp_core(group);

			if (!mali_group_is_virtual(group)) {
				if (NULL != pp_core) {
					if (0 == pp_version) {
						/* Retrieve PP version from the first available PP core */
						pp_version = mali_pp_core_get_version(pp_core);
					}

					if (NULL != virtual_group) {
						mali_executor_lock();
						mali_group_add_group(virtual_group, group);
						mali_executor_unlock();
					} else {
						_mali_osk_list_add(&group->executor_list, &group_list_inactive);
						group_list_inactive_count++;
					}

					num_physical_pp_cores_total++;
				} else {
					MALI_DEBUG_ASSERT_POINTER(gp_core);

					if (0 == gp_version) {
						/* Retrieve GP version */
						gp_version = mali_gp_core_get_version(gp_core);
					}

					gp_group = group;
					gp_group_state = EXEC_STATE_INACTIVE;
				}

			}
		}
	}

	num_physical_pp_cores_enabled = num_physical_pp_cores_total;
}

void mali_executor_depopulate(void)
{
	struct mali_group *group;
	struct mali_group *temp;

	MALI_DEBUG_ASSERT(EXEC_STATE_WORKING != gp_group_state);

	if (NULL != gp_group) {
		mali_group_delete(gp_group);
		gp_group = NULL;
	}

	MALI_DEBUG_ASSERT(EXEC_STATE_WORKING != virtual_group_state);

	if (NULL != virtual_group) {
		mali_group_delete(virtual_group);
		virtual_group = NULL;
	}

	MALI_DEBUG_ASSERT(_mali_osk_list_empty(&group_list_working));

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_idle, struct mali_group, executor_list) {
		mali_group_delete(group);
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_inactive, struct mali_group, executor_list) {
		mali_group_delete(group);
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_disabled, struct mali_group, executor_list) {
		mali_group_delete(group);
	}
}

void mali_executor_suspend(void)
{
	mali_executor_lock();

	/* Increment the pause_count so that no more jobs will be scheduled */
	pause_count++;

	mali_executor_unlock();

	_mali_osk_wait_queue_wait_event(executor_working_wait_queue,
					mali_executor_is_suspended, NULL);

	/*
	 * mali_executor_complete_XX() leaves jobs in idle state.
	 * deactivate option is used when we are going to power down
	 * the entire GPU (OS suspend) and want a consistent SW vs HW
	 * state.
	 */
	mali_executor_lock();

	mali_executor_deactivate_list_idle(MALI_TRUE);

	/*
	 * The following steps are used to deactive all of activated
	 * (MALI_GROUP_STATE_ACTIVE) and activating (MALI_GROUP
	 * _STAET_ACTIVATION_PENDING) groups, to make sure the variable
	 * pd_mask_wanted is equal with 0. */
	if (MALI_GROUP_STATE_INACTIVE != mali_group_get_state(gp_group)) {
		gp_group_state = EXEC_STATE_INACTIVE;
		mali_group_deactivate(gp_group);
	}

	if (mali_executor_has_virtual_group()) {
		if (MALI_GROUP_STATE_INACTIVE
		    != mali_group_get_state(virtual_group)) {
			virtual_group_state = EXEC_STATE_INACTIVE;
			mali_group_deactivate(virtual_group);
		}
	}

	if (0 < group_list_inactive_count) {
		struct mali_group *group;
		struct mali_group *temp;

		_MALI_OSK_LIST_FOREACHENTRY(group, temp,
					    &group_list_inactive,
					    struct mali_group, executor_list) {
			if (MALI_GROUP_STATE_ACTIVATION_PENDING
			    == mali_group_get_state(group)) {
				mali_group_deactivate(group);
			}

			/*
			 * On mali-450 platform, we may have physical group in the group inactive
			 * list, and its state is MALI_GROUP_STATE_ACTIVATION_PENDING, so we only
			 * deactivate it is not enough, we still also need add it back to virtual group.
			 * And now, virtual group must be in INACTIVE state, so it's safe to add
			 * physical group to virtual group at this point.
			 */
			if (NULL != virtual_group) {
				_mali_osk_list_delinit(&group->executor_list);
				group_list_inactive_count--;

				mali_group_add_group(virtual_group, group);
			}
		}
	}

	mali_executor_unlock();
}

void mali_executor_resume(void)
{
	mali_executor_lock();

	/* Decrement pause_count to allow scheduling again (if it reaches 0) */
	pause_count--;
	if (0 == pause_count) {
		mali_executor_schedule();
	}

	mali_executor_unlock();
}

u32 mali_executor_get_num_cores_total(void)
{
	return num_physical_pp_cores_total;
}

u32 mali_executor_get_num_cores_enabled(void)
{
	return num_physical_pp_cores_enabled;
}

struct mali_pp_core *mali_executor_get_virtual_pp(void)
{
	MALI_DEBUG_ASSERT_POINTER(virtual_group);
	MALI_DEBUG_ASSERT_POINTER(virtual_group->pp_core);
	return virtual_group->pp_core;
}

struct mali_group *mali_executor_get_virtual_group(void)
{
	return virtual_group;
}

void mali_executor_zap_all_active(struct mali_session_data *session)
{
	struct mali_group *group;
	struct mali_group *temp;
	mali_bool ret;

	mali_executor_lock();

	/*
	 * This function is a bit complicated because
	 * mali_group_zap_session() can fail. This only happens because the
	 * group is in an unhandled page fault status.
	 * We need to make sure this page fault is handled before we return,
	 * so that we know every single outstanding MMU transactions have
	 * completed. This will allow caller to safely remove physical pages
	 * when we have returned.
	 */

	MALI_DEBUG_ASSERT(NULL != gp_group);
	ret = mali_group_zap_session(gp_group, session);
	if (MALI_FALSE == ret) {
		struct mali_gp_job *gp_job = NULL;

		mali_executor_complete_group(gp_group, MALI_FALSE, &gp_job, NULL);

		MALI_DEBUG_ASSERT_POINTER(gp_job);

		/* GP job completed, make sure it is freed */
		mali_scheduler_complete_gp_job(gp_job, MALI_FALSE,
					       MALI_TRUE, MALI_TRUE);
	}

	if (mali_executor_has_virtual_group()) {
		ret = mali_group_zap_session(virtual_group, session);
		if (MALI_FALSE == ret) {
			struct mali_pp_job *pp_job = NULL;

			mali_executor_complete_group(virtual_group, MALI_FALSE, NULL, &pp_job);

			if (NULL != pp_job) {
				/* PP job completed, make sure it is freed */
				mali_scheduler_complete_pp_job(pp_job, 0,
							       MALI_TRUE, MALI_TRUE);
			}
		}
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_working,
				    struct mali_group, executor_list) {
		ret = mali_group_zap_session(group, session);
		if (MALI_FALSE == ret) {
			ret = mali_group_zap_session(group, session);
			if (MALI_FALSE == ret) {
				struct mali_pp_job *pp_job = NULL;

				mali_executor_complete_group(group, MALI_FALSE, NULL, &pp_job);

				if (NULL != pp_job) {
					/* PP job completed, free it */
					mali_scheduler_complete_pp_job(pp_job,
								       0, MALI_TRUE,
								       MALI_TRUE);
				}
			}
		}
	}

	mali_executor_unlock();
}

void mali_executor_schedule_from_mask(mali_scheduler_mask mask, mali_bool deferred_schedule)
{
	if (MALI_SCHEDULER_MASK_EMPTY != mask) {
		if (MALI_TRUE == deferred_schedule) {
			_mali_osk_wq_schedule_work_high_pri(executor_wq_high_pri);
		} else {
			/* Schedule from this thread*/
			mali_executor_lock();
			mali_executor_schedule();
			mali_executor_unlock();
		}
	}
}

_mali_osk_errcode_t mali_executor_interrupt_gp(struct mali_group *group,
		mali_bool in_upper_half)
{
	enum mali_interrupt_result int_result;
	mali_bool time_out = MALI_FALSE;

	MALI_DEBUG_PRINT(4, ("Executor: GP interrupt from %s in %s half\n",
			     mali_group_core_description(group),
			     in_upper_half ? "upper" : "bottom"));

	mali_executor_lock();
	if (!mali_group_is_working(group)) {
		/* Not working, so nothing to do */
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	}

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(mali_group_is_working(group));

	if (mali_group_has_timed_out(group)) {
		int_result = MALI_INTERRUPT_RESULT_ERROR;
		time_out = MALI_TRUE;
		MALI_PRINT(("Executor GP: Job %d Timeout on %s\n",
			    mali_gp_job_get_id(group->gp_running_job),
			    mali_group_core_description(group)));
	} else {
		int_result = mali_group_get_interrupt_result_gp(group);
		if (MALI_INTERRUPT_RESULT_NONE == int_result) {
			mali_executor_unlock();
			return _MALI_OSK_ERR_FAULT;
		}
	}

#if defined(CONFIG_MALI_SHARED_INTERRUPTS)
	if (MALI_INTERRUPT_RESULT_NONE == int_result) {
		/* No interrupts signalled, so nothing to do */
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	}
#else
	MALI_DEBUG_ASSERT(MALI_INTERRUPT_RESULT_NONE != int_result);
#endif

	mali_group_mask_all_interrupts_gp(group);

	if (MALI_INTERRUPT_RESULT_SUCCESS_VS == int_result) {
		if (mali_group_gp_is_active(group)) {
			/* Only VS completed so far, while PLBU is still active */

			/* Enable all but the current interrupt */
			mali_group_enable_interrupts_gp(group, int_result);

			mali_executor_unlock();
			return _MALI_OSK_ERR_OK;
		}
	} else if (MALI_INTERRUPT_RESULT_SUCCESS_PLBU == int_result) {
		if (mali_group_gp_is_active(group)) {
			/* Only PLBU completed so far, while VS is still active */

			/* Enable all but the current interrupt */
			mali_group_enable_interrupts_gp(group, int_result);

			mali_executor_unlock();
			return _MALI_OSK_ERR_OK;
		}
	} else if (MALI_INTERRUPT_RESULT_OOM == int_result) {
		struct mali_gp_job *job = mali_group_get_running_gp_job(group);

		/* PLBU out of mem */
		MALI_DEBUG_PRINT(3, ("Executor: PLBU needs more heap memory\n"));

#if defined(CONFIG_MALI400_PROFILING)
		/* Give group a chance to generate a SUSPEND event */
		mali_group_oom(group);
#endif

		/*
		 * no need to hold interrupt raised while
		 * waiting for more memory.
		 */
		mali_executor_send_gp_oom_to_user(job);

		mali_executor_unlock();

		return _MALI_OSK_ERR_OK;
	}

	/* We should now have a real interrupt to handle */

	MALI_DEBUG_PRINT(4, ("Executor: Group %s completed with %s\n",
			     mali_group_core_description(group),
			     (MALI_INTERRUPT_RESULT_ERROR == int_result) ?
			     "ERROR" : "success"));

	if (in_upper_half && MALI_INTERRUPT_RESULT_ERROR == int_result) {
		/* Don't bother to do processing of errors in upper half */
		mali_executor_unlock();

		if (MALI_FALSE == time_out) {
			mali_group_schedule_bottom_half_gp(group);
		}
	} else {
		struct mali_gp_job *job;
		mali_bool success;

		if (MALI_TRUE == time_out) {
			mali_group_dump_status(group);
		}

		success = (int_result != MALI_INTERRUPT_RESULT_ERROR) ?
			  MALI_TRUE : MALI_FALSE;

		mali_executor_complete_group(group, success, &job, NULL);

		mali_executor_unlock();

		/* GP jobs always fully complete */
		MALI_DEBUG_ASSERT(NULL != job);

		/* This will notify user space and close the job object */
		mali_scheduler_complete_gp_job(job, success,
					       MALI_TRUE, MALI_TRUE);
	}

	return _MALI_OSK_ERR_OK;
}

_mali_osk_errcode_t mali_executor_interrupt_pp(struct mali_group *group,
		mali_bool in_upper_half)
{
	enum mali_interrupt_result int_result;
	mali_bool time_out = MALI_FALSE;

	MALI_DEBUG_PRINT(4, ("Executor: PP interrupt from %s in %s half\n",
			     mali_group_core_description(group),
			     in_upper_half ? "upper" : "bottom"));

	mali_executor_lock();

	if (!mali_group_is_working(group)) {
		/* Not working, so nothing to do */
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	}

	if (in_upper_half) {
		if (mali_group_is_in_virtual(group)) {
			/* Child groups should never handle PP interrupts */
			MALI_DEBUG_ASSERT(!mali_group_has_timed_out(group));
			mali_executor_unlock();
			return _MALI_OSK_ERR_FAULT;
		}
	}
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(mali_group_is_working(group));
	MALI_DEBUG_ASSERT(!mali_group_is_in_virtual(group));

	if (mali_group_has_timed_out(group)) {
		int_result = MALI_INTERRUPT_RESULT_ERROR;
		time_out = MALI_TRUE;
		MALI_PRINT(("Executor PP: Job %d Timeout on %s\n",
			    mali_pp_job_get_id(group->pp_running_job),
			    mali_group_core_description(group)));
	} else {
		int_result = mali_group_get_interrupt_result_pp(group);
		if (MALI_INTERRUPT_RESULT_NONE == int_result) {
			mali_executor_unlock();
			return _MALI_OSK_ERR_FAULT;
		}
	}

#if defined(CONFIG_MALI_SHARED_INTERRUPTS)
	if (MALI_INTERRUPT_RESULT_NONE == int_result) {
		/* No interrupts signalled, so nothing to do */
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	} else if (MALI_INTERRUPT_RESULT_SUCCESS == int_result) {
		if (mali_group_is_virtual(group) && mali_group_pp_is_active(group)) {
			/* Some child groups are still working, so nothing to do right now */
			mali_executor_unlock();
			return _MALI_OSK_ERR_FAULT;
		}
	}
#else
	MALI_DEBUG_ASSERT(MALI_INTERRUPT_RESULT_NONE != int_result);
#endif

	/* We should now have a real interrupt to handle */

	MALI_DEBUG_PRINT(4, ("Executor: Group %s completed with %s\n",
			     mali_group_core_description(group),
			     (MALI_INTERRUPT_RESULT_ERROR == int_result) ?
			     "ERROR" : "success"));

	if (in_upper_half && MALI_INTERRUPT_RESULT_ERROR == int_result) {
		/* Don't bother to do processing of errors in upper half */
		mali_group_mask_all_interrupts_pp(group);
		mali_executor_unlock();

		if (MALI_FALSE == time_out) {
			mali_group_schedule_bottom_half_pp(group);
		}
	} else {
		struct mali_pp_job *job = NULL;
		mali_bool success;

		if (MALI_TRUE == time_out) {
			mali_group_dump_status(group);
		}

		success = (int_result == MALI_INTERRUPT_RESULT_SUCCESS) ?
			  MALI_TRUE : MALI_FALSE;

		mali_executor_complete_group(group, success, NULL, &job);

		mali_executor_unlock();

		if (NULL != job) {
			/* Notify user space and close the job object */
			mali_scheduler_complete_pp_job(job,
						       num_physical_pp_cores_total,
						       MALI_TRUE, MALI_TRUE);
		}
	}

	return _MALI_OSK_ERR_OK;
}

_mali_osk_errcode_t mali_executor_interrupt_mmu(struct mali_group *group,
		mali_bool in_upper_half)
{
	enum mali_interrupt_result int_result;

	MALI_DEBUG_PRINT(4, ("Executor: MMU interrupt from %s in %s half\n",
			     mali_group_core_description(group),
			     in_upper_half ? "upper" : "bottom"));

	mali_executor_lock();
	if (!mali_group_is_working(group)) {
		/* Not working, so nothing to do */
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	}

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(mali_group_is_working(group));

	int_result = mali_group_get_interrupt_result_mmu(group);
	if (MALI_INTERRUPT_RESULT_NONE == int_result) {
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	}

#if defined(CONFIG_MALI_SHARED_INTERRUPTS)
	if (MALI_INTERRUPT_RESULT_NONE == int_result) {
		/* No interrupts signalled, so nothing to do */
		mali_executor_unlock();
		return _MALI_OSK_ERR_FAULT;
	}
#else
	MALI_DEBUG_ASSERT(MALI_INTERRUPT_RESULT_ERROR == int_result);
#endif

	/* We should now have a real interrupt to handle */

	if (in_upper_half) {
		/* Don't bother to do processing of errors in upper half */

		struct mali_group *parent = group->parent_group;

		mali_mmu_mask_all_interrupts(group->mmu);

		mali_executor_unlock();

		if (NULL == parent) {
			mali_group_schedule_bottom_half_mmu(group);
		} else {
			mali_group_schedule_bottom_half_mmu(parent);
		}

	} else {
		struct mali_gp_job *gp_job = NULL;
		struct mali_pp_job *pp_job = NULL;

#ifdef DEBUG

		u32 fault_address = mali_mmu_get_page_fault_addr(group->mmu);
		u32 status = mali_mmu_get_status(group->mmu);
		MALI_DEBUG_PRINT(2, ("Executor: Mali page fault detected at 0x%x from bus id %d of type %s on %s\n",
				     (void *)(uintptr_t)fault_address,
				     (status >> 6) & 0x1F,
				     (status & 32) ? "write" : "read",
				     group->mmu->hw_core.description));
		MALI_DEBUG_PRINT(3, ("Executor: MMU rawstat = 0x%08X, MMU status = 0x%08X\n",
				     mali_mmu_get_rawstat(group->mmu), status));
		mali_mmu_pagedir_diag(mali_session_get_page_directory(group->session), fault_address);
#endif

		mali_executor_complete_group(group, MALI_FALSE, &gp_job, &pp_job);

		mali_executor_unlock();

		if (NULL != gp_job) {
			MALI_DEBUG_ASSERT(NULL == pp_job);

			/* Notify user space and close the job object */
			mali_scheduler_complete_gp_job(gp_job, MALI_FALSE,
						       MALI_TRUE, MALI_TRUE);
		} else if (NULL != pp_job) {
			MALI_DEBUG_ASSERT(NULL == gp_job);

			/* Notify user space and close the job object */
			mali_scheduler_complete_pp_job(pp_job,
						       num_physical_pp_cores_total,
						       MALI_TRUE, MALI_TRUE);
		}
	}

	return _MALI_OSK_ERR_OK;
}

void mali_executor_group_power_up(struct mali_group *groups[], u32 num_groups)
{
	u32 i;
	mali_bool child_groups_activated = MALI_FALSE;
	mali_bool do_schedule = MALI_FALSE;
#if defined(DEBUG)
	u32 num_activated = 0;
#endif

	MALI_DEBUG_ASSERT_POINTER(groups);
	MALI_DEBUG_ASSERT(0 < num_groups);

	mali_executor_lock();

	MALI_DEBUG_PRINT(3, ("Executor: powering up %u groups\n", num_groups));

	for (i = 0; i < num_groups; i++) {
		MALI_DEBUG_PRINT(3, ("Executor: powering up group %s\n",
				     mali_group_core_description(groups[i])));

		mali_group_power_up(groups[i]);

		if ((MALI_GROUP_STATE_ACTIVATION_PENDING != mali_group_get_state(groups[i]) ||
		     (MALI_TRUE != mali_executor_group_is_in_state(groups[i], EXEC_STATE_INACTIVE)))) {
			/* nothing more to do for this group */
			continue;
		}

		MALI_DEBUG_PRINT(3, ("Executor: activating group %s\n",
				     mali_group_core_description(groups[i])));

#if defined(DEBUG)
		num_activated++;
#endif

		if (mali_group_is_in_virtual(groups[i])) {
			/*
			 * At least one child group of virtual group is powered on.
			 */
			child_groups_activated = MALI_TRUE;
		} else if (MALI_FALSE == mali_group_is_virtual(groups[i])) {
			/* Set gp and pp not in virtual to active. */
			mali_group_set_active(groups[i]);
		}

		/* Move group from inactive to idle list */
		if (groups[i] == gp_group) {
			MALI_DEBUG_ASSERT(EXEC_STATE_INACTIVE ==
					  gp_group_state);
			gp_group_state = EXEC_STATE_IDLE;
		} else if (MALI_FALSE == mali_group_is_in_virtual(groups[i])
			   && MALI_FALSE == mali_group_is_virtual(groups[i])) {
			MALI_DEBUG_ASSERT(MALI_TRUE == mali_executor_group_is_in_state(groups[i],
					  EXEC_STATE_INACTIVE));

			mali_executor_change_state_pp_physical(groups[i],
							       &group_list_inactive,
							       &group_list_inactive_count,
							       &group_list_idle,
							       &group_list_idle_count);
		}

		do_schedule = MALI_TRUE;
	}

	if (mali_executor_has_virtual_group() &&
	    MALI_TRUE == child_groups_activated &&
	    MALI_GROUP_STATE_ACTIVATION_PENDING ==
	    mali_group_get_state(virtual_group)) {
		/*
		 * Try to active virtual group while it may be not sucessful every time,
		 * because there is one situation that not all of child groups are powered on
		 * in one time and virtual group is in activation pending state.
		 */
		if (mali_group_set_active(virtual_group)) {
			/* Move group from inactive to idle */
			MALI_DEBUG_ASSERT(EXEC_STATE_INACTIVE ==
					  virtual_group_state);
			virtual_group_state = EXEC_STATE_IDLE;

			MALI_DEBUG_PRINT(3, ("Executor: powering up %u groups completed, %u  physical activated, 1 virtual activated.\n", num_groups, num_activated));
		} else {
			MALI_DEBUG_PRINT(3, ("Executor: powering up %u groups completed, %u physical activated\n", num_groups, num_activated));
		}
	} else {
		MALI_DEBUG_PRINT(3, ("Executor: powering up %u groups completed, %u physical activated\n", num_groups, num_activated));
	}

	if (MALI_TRUE == do_schedule) {
		/* Trigger a schedule */
		mali_executor_schedule();
	}

	mali_executor_unlock();
}

void mali_executor_group_power_down(struct mali_group *groups[],
				    u32 num_groups)
{
	u32 i;

	MALI_DEBUG_ASSERT_POINTER(groups);
	MALI_DEBUG_ASSERT(0 < num_groups);

	mali_executor_lock();

	MALI_DEBUG_PRINT(3, ("Executor: powering down %u groups\n", num_groups));

	for (i = 0; i < num_groups; i++) {
		/* Groups must be either disabled or inactive. while for virtual group,
		 * it maybe in empty state, because when we meet pm_runtime_suspend,
		 * virtual group could be powered off, and before we acquire mali_executor_lock,
		 * we must release mali_pm_state_lock, if there is a new physical job was queued,
		 * all of physical groups in virtual group could be pulled out, so we only can
		 * powered down an empty virtual group. Those physical groups will be powered
		 * up in following pm_runtime_resume callback function.
		 */
		MALI_DEBUG_ASSERT(mali_executor_group_is_in_state(groups[i],
				  EXEC_STATE_DISABLED) ||
				  mali_executor_group_is_in_state(groups[i],
						  EXEC_STATE_INACTIVE) ||
				  mali_executor_group_is_in_state(groups[i],
						  EXEC_STATE_EMPTY));

		MALI_DEBUG_PRINT(3, ("Executor: powering down group %s\n",
				     mali_group_core_description(groups[i])));

		mali_group_power_down(groups[i]);
	}

	MALI_DEBUG_PRINT(3, ("Executor: powering down %u groups completed\n", num_groups));

	mali_executor_unlock();
}

void mali_executor_abort_session(struct mali_session_data *session)
{
	struct mali_group *group;
	struct mali_group *tmp_group;

	MALI_DEBUG_ASSERT_POINTER(session);
	MALI_DEBUG_ASSERT(session->is_aborting);

	MALI_DEBUG_PRINT(3,
			 ("Executor: Aborting all jobs from session 0x%08X.\n",
			  session));

	mali_executor_lock();

	if (mali_group_get_session(gp_group) == session) {
		if (EXEC_STATE_WORKING == gp_group_state) {
			struct mali_gp_job *gp_job = NULL;

			mali_executor_complete_group(gp_group, MALI_FALSE, &gp_job, NULL);

			MALI_DEBUG_ASSERT_POINTER(gp_job);

			/* GP job completed, make sure it is freed */
			mali_scheduler_complete_gp_job(gp_job, MALI_FALSE,
						       MALI_TRUE, MALI_TRUE);
		} else {
			/* Same session, but not working, so just clear it */
			mali_group_clear_session(gp_group);
		}
	}

	if (mali_executor_has_virtual_group()) {
		if (EXEC_STATE_WORKING == virtual_group_state
		    && mali_group_get_session(virtual_group) == session) {
			struct mali_pp_job *pp_job = NULL;

			mali_executor_complete_group(virtual_group, MALI_FALSE, NULL, &pp_job);

			if (NULL != pp_job) {
				/* PP job completed, make sure it is freed */
				mali_scheduler_complete_pp_job(pp_job, 0,
							       MALI_TRUE, MALI_TRUE);
			}
		}
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, tmp_group, &group_list_working,
				    struct mali_group, executor_list) {
		if (mali_group_get_session(group) == session) {
			struct mali_pp_job *pp_job = NULL;

			mali_executor_complete_group(group, MALI_FALSE, NULL, &pp_job);

			if (NULL != pp_job) {
				/* PP job completed, make sure it is freed */
				mali_scheduler_complete_pp_job(pp_job, 0,
							       MALI_TRUE, MALI_TRUE);
			}
		}
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, tmp_group, &group_list_idle, struct mali_group, executor_list) {
		mali_group_clear_session(group);
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, tmp_group, &group_list_inactive, struct mali_group, executor_list) {
		mali_group_clear_session(group);
	}

	_MALI_OSK_LIST_FOREACHENTRY(group, tmp_group, &group_list_disabled, struct mali_group, executor_list) {
		mali_group_clear_session(group);
	}

	mali_executor_unlock();
}


void mali_executor_core_scaling_enable(void)
{
	/* PS: Core scaling is by default enabled */
	core_scaling_enabled = MALI_TRUE;
}

void mali_executor_core_scaling_disable(void)
{
	core_scaling_enabled = MALI_FALSE;
}

mali_bool mali_executor_core_scaling_is_enabled(void)
{
	return core_scaling_enabled;
}

void mali_executor_group_enable(struct mali_group *group)
{
	MALI_DEBUG_ASSERT_POINTER(group);

	mali_executor_lock();

	if ((NULL != mali_group_get_gp_core(group) || NULL != mali_group_get_pp_core(group))
	    && (mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED))) {
		mali_executor_group_enable_internal(group);
	}

	mali_executor_schedule();
	mali_executor_unlock();

	_mali_osk_wq_schedule_work(executor_wq_notify_core_change);
}

/*
 * If a physical group is inactive or idle, we should disable it immediately,
 * if group is in virtual, and virtual group is idle, disable given physical group in it.
 */
void mali_executor_group_disable(struct mali_group *group)
{
	MALI_DEBUG_ASSERT_POINTER(group);

	mali_executor_lock();

	if ((NULL != mali_group_get_gp_core(group) || NULL != mali_group_get_pp_core(group))
	    && (!mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED))) {
		mali_executor_group_disable_internal(group);
	}

	mali_executor_schedule();
	mali_executor_unlock();

	_mali_osk_wq_schedule_work(executor_wq_notify_core_change);
}

mali_bool mali_executor_group_is_disabled(struct mali_group *group)
{
	/* NB: This function is not optimized for time critical usage */

	mali_bool ret;

	MALI_DEBUG_ASSERT_POINTER(group);

	mali_executor_lock();
	ret = mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED);
	mali_executor_unlock();

	return ret;
}

int mali_executor_set_perf_level(unsigned int target_core_nr, mali_bool override)
{
	if (target_core_nr == num_physical_pp_cores_enabled) return 0;
	if (MALI_FALSE == core_scaling_enabled && MALI_FALSE == override) return -EPERM;
	if (target_core_nr > num_physical_pp_cores_total) return -EINVAL;
	if (0 == target_core_nr) return -EINVAL;

	mali_executor_core_scale(target_core_nr);

	_mali_osk_wq_schedule_work(executor_wq_notify_core_change);

	return 0;
}

#if MALI_STATE_TRACKING
u32 mali_executor_dump_state(char *buf, u32 size)
{
	int n = 0;
	struct mali_group *group;
	struct mali_group *temp;

	mali_executor_lock();

	switch (gp_group_state) {
	case EXEC_STATE_INACTIVE:
		n += _mali_osk_snprintf(buf + n, size - n,
					"GP group is in state INACTIVE\n");
		break;
	case EXEC_STATE_IDLE:
		n += _mali_osk_snprintf(buf + n, size - n,
					"GP group is in state IDLE\n");
		break;
	case EXEC_STATE_WORKING:
		n += _mali_osk_snprintf(buf + n, size - n,
					"GP group is in state WORKING\n");
		break;
	default:
		n += _mali_osk_snprintf(buf + n, size - n,
					"GP group is in unknown/illegal state %u\n",
					gp_group_state);
		break;
	}

	n += mali_group_dump_state(gp_group, buf + n, size - n);

	n += _mali_osk_snprintf(buf + n, size - n,
				"Physical PP groups in WORKING state (count = %u):\n",
				group_list_working_count);

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_working, struct mali_group, executor_list) {
		n += mali_group_dump_state(group, buf + n, size - n);
	}

	n += _mali_osk_snprintf(buf + n, size - n,
				"Physical PP groups in IDLE state (count = %u):\n",
				group_list_idle_count);

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_idle, struct mali_group, executor_list) {
		n += mali_group_dump_state(group, buf + n, size - n);
	}

	n += _mali_osk_snprintf(buf + n, size - n,
				"Physical PP groups in INACTIVE state (count = %u):\n",
				group_list_inactive_count);

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_inactive, struct mali_group, executor_list) {
		n += mali_group_dump_state(group, buf + n, size - n);
	}

	n += _mali_osk_snprintf(buf + n, size - n,
				"Physical PP groups in DISABLED state (count = %u):\n",
				group_list_disabled_count);

	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_disabled, struct mali_group, executor_list) {
		n += mali_group_dump_state(group, buf + n, size - n);
	}

	if (mali_executor_has_virtual_group()) {
		switch (virtual_group_state) {
		case EXEC_STATE_EMPTY:
			n += _mali_osk_snprintf(buf + n, size - n,
						"Virtual PP group is in state EMPTY\n");
			break;
		case EXEC_STATE_INACTIVE:
			n += _mali_osk_snprintf(buf + n, size - n,
						"Virtual PP group is in state INACTIVE\n");
			break;
		case EXEC_STATE_IDLE:
			n += _mali_osk_snprintf(buf + n, size - n,
						"Virtual PP group is in state IDLE\n");
			break;
		case EXEC_STATE_WORKING:
			n += _mali_osk_snprintf(buf + n, size - n,
						"Virtual PP group is in state WORKING\n");
			break;
		default:
			n += _mali_osk_snprintf(buf + n, size - n,
						"Virtual PP group is in unknown/illegal state %u\n",
						virtual_group_state);
			break;
		}

		n += mali_group_dump_state(virtual_group, buf + n, size - n);
	}

	mali_executor_unlock();

	n += _mali_osk_snprintf(buf + n, size - n, "\n");

	return n;
}
#endif

_mali_osk_errcode_t _mali_ukk_get_pp_number_of_cores(_mali_uk_get_pp_number_of_cores_s *args)
{
	MALI_DEBUG_ASSERT_POINTER(args);
	MALI_DEBUG_ASSERT(NULL != (void *)(uintptr_t)args->ctx);
	args->number_of_total_cores = num_physical_pp_cores_total;
	args->number_of_enabled_cores = num_physical_pp_cores_enabled;
	return _MALI_OSK_ERR_OK;
}

_mali_osk_errcode_t _mali_ukk_get_pp_core_version(_mali_uk_get_pp_core_version_s *args)
{
	MALI_DEBUG_ASSERT_POINTER(args);
	MALI_DEBUG_ASSERT(NULL != (void *)(uintptr_t)args->ctx);
	args->version = pp_version;
	return _MALI_OSK_ERR_OK;
}

_mali_osk_errcode_t _mali_ukk_get_gp_number_of_cores(_mali_uk_get_gp_number_of_cores_s *args)
{
	MALI_DEBUG_ASSERT_POINTER(args);
	MALI_DEBUG_ASSERT(NULL != (void *)(uintptr_t)args->ctx);
	args->number_of_cores = 1;
	return _MALI_OSK_ERR_OK;
}

_mali_osk_errcode_t _mali_ukk_get_gp_core_version(_mali_uk_get_gp_core_version_s *args)
{
	MALI_DEBUG_ASSERT_POINTER(args);
	MALI_DEBUG_ASSERT(NULL != (void *)(uintptr_t)args->ctx);
	args->version = gp_version;
	return _MALI_OSK_ERR_OK;
}

_mali_osk_errcode_t _mali_ukk_gp_suspend_response(_mali_uk_gp_suspend_response_s *args)
{
	struct mali_session_data *session;
	struct mali_gp_job *job;

	MALI_DEBUG_ASSERT_POINTER(args);
	MALI_DEBUG_ASSERT(NULL != (void *)(uintptr_t)args->ctx);

	session = (struct mali_session_data *)(uintptr_t)args->ctx;

	if (_MALIGP_JOB_RESUME_WITH_NEW_HEAP == args->code) {
		_mali_osk_notification_t *new_notification = NULL;

		new_notification = _mali_osk_notification_create(
					   _MALI_NOTIFICATION_GP_STALLED,
					   sizeof(_mali_uk_gp_job_suspended_s));

		if (NULL != new_notification) {
			MALI_DEBUG_PRINT(3, ("Executor: Resuming job %u with new heap; 0x%08X - 0x%08X\n",
					     args->cookie, args->arguments[0], args->arguments[1]));

			mali_executor_lock();

			/* Resume the job in question if it is still running */
			job = mali_group_get_running_gp_job(gp_group);
			if (NULL != job &&
			    args->cookie == mali_gp_job_get_id(job) &&
			    session == mali_gp_job_get_session(job)) {
				/*
				 * Correct job is running, resume with new heap
				 */

				mali_gp_job_set_oom_notification(job,
								 new_notification);

				/* This will also re-enable interrupts */
				mali_group_resume_gp_with_new_heap(gp_group,
								   args->cookie,
								   args->arguments[0],
								   args->arguments[1]);

				mali_executor_unlock();
				return _MALI_OSK_ERR_OK;
			} else {
				MALI_DEBUG_PRINT(2, ("Executor: Unable to resume  gp job becasue gp time out or any other unexpected reason!\n"));

				_mali_osk_notification_delete(new_notification);

				mali_executor_unlock();
				return _MALI_OSK_ERR_FAULT;
			}
		} else {
			MALI_PRINT_ERROR(("Executor: Failed to allocate notification object. Will abort GP job.\n"));
		}
	} else {
		MALI_DEBUG_PRINT(2, ("Executor: Aborting job %u, no new heap provided\n", args->cookie));
	}

	mali_executor_lock();

	/* Abort the job in question if it is still running */
	job = mali_group_get_running_gp_job(gp_group);
	if (NULL != job &&
	    args->cookie == mali_gp_job_get_id(job) &&
	    session == mali_gp_job_get_session(job)) {
		/* Correct job is still running */
		struct mali_gp_job *job_done = NULL;

		mali_executor_complete_group(gp_group, MALI_FALSE, &job_done, NULL);

		/* The same job should have completed */
		MALI_DEBUG_ASSERT(job_done == job);

		/* GP job completed, make sure it is freed */
		mali_scheduler_complete_gp_job(job_done, MALI_FALSE,
					       MALI_TRUE, MALI_TRUE);
	}

	mali_executor_unlock();
	return _MALI_OSK_ERR_FAULT;
}


/*
 * ---------- Implementation of static functions ----------
 */

void mali_executor_lock(void)
{
	_mali_osk_spinlock_irq_lock(mali_executor_lock_obj);
	MALI_DEBUG_PRINT(5, ("Executor: lock taken\n"));
}

void mali_executor_unlock(void)
{
	MALI_DEBUG_PRINT(5, ("Executor: Releasing lock\n"));
	_mali_osk_spinlock_irq_unlock(mali_executor_lock_obj);
}

static mali_bool mali_executor_is_suspended(void *data)
{
	mali_bool ret;

	/* This callback does not use the data pointer. */
	MALI_IGNORE(data);

	mali_executor_lock();

	ret = pause_count > 0 && !mali_executor_is_working();

	mali_executor_unlock();

	return ret;
}

static mali_bool mali_executor_is_working()
{
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	return (0 != group_list_working_count ||
		EXEC_STATE_WORKING == gp_group_state ||
		EXEC_STATE_WORKING == virtual_group_state);
}

static void mali_executor_disable_empty_virtual(void)
{
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(virtual_group_state != EXEC_STATE_EMPTY);
	MALI_DEBUG_ASSERT(virtual_group_state != EXEC_STATE_WORKING);

	if (mali_group_is_empty(virtual_group)) {
		virtual_group_state = EXEC_STATE_EMPTY;
	}
}

static mali_bool mali_executor_physical_rejoin_virtual(struct mali_group *group)
{
	mali_bool trigger_pm_update = MALI_FALSE;

	MALI_DEBUG_ASSERT_POINTER(group);
	/* Only rejoining after job has completed (still active) */
	MALI_DEBUG_ASSERT(MALI_GROUP_STATE_ACTIVE ==
			  mali_group_get_state(group));
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(MALI_TRUE == mali_executor_has_virtual_group());
	MALI_DEBUG_ASSERT(MALI_FALSE == mali_group_is_virtual(group));

	/* Make sure group and virtual group have same status */

	if (MALI_GROUP_STATE_INACTIVE == mali_group_get_state(virtual_group)) {
		if (mali_group_deactivate(group)) {
			trigger_pm_update = MALI_TRUE;
		}

		if (virtual_group_state == EXEC_STATE_EMPTY) {
			virtual_group_state = EXEC_STATE_INACTIVE;
		}
	} else if (MALI_GROUP_STATE_ACTIVATION_PENDING ==
		   mali_group_get_state(virtual_group)) {
		/*
		 * Activation is pending for virtual group, leave
		 * this child group as active.
		 */
		if (virtual_group_state == EXEC_STATE_EMPTY) {
			virtual_group_state = EXEC_STATE_INACTIVE;
		}
	} else {
		MALI_DEBUG_ASSERT(MALI_GROUP_STATE_ACTIVE ==
				  mali_group_get_state(virtual_group));

		if (virtual_group_state == EXEC_STATE_EMPTY) {
			virtual_group_state = EXEC_STATE_IDLE;
		}
	}

	/* Remove group from idle list */
	MALI_DEBUG_ASSERT(mali_executor_group_is_in_state(group,
			  EXEC_STATE_IDLE));
	_mali_osk_list_delinit(&group->executor_list);
	group_list_idle_count--;

	/*
	 * And finally rejoin the virtual group
	 * group will start working on same job as virtual_group,
	 * if virtual_group is working on a job
	 */
	mali_group_add_group(virtual_group, group);

	return trigger_pm_update;
}

static mali_bool mali_executor_has_virtual_group(void)
{
#if (defined(CONFIG_MALI450) || defined(CONFIG_MALI470))
	return (NULL != virtual_group) ? MALI_TRUE : MALI_FALSE;
#else
	return MALI_FALSE;
#endif /* (defined(CONFIG_MALI450) || defined(CONFIG_MALI470)) */
}

static mali_bool mali_executor_virtual_group_is_usable(void)
{
#if (defined(CONFIG_MALI450) || defined(CONFIG_MALI470))
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	return ((EXEC_STATE_INACTIVE == virtual_group_state ||
		 EXEC_STATE_IDLE == virtual_group_state) && (virtual_group->state != MALI_GROUP_STATE_ACTIVATION_PENDING)) ?
	       MALI_TRUE : MALI_FALSE;
#else
	return MALI_FALSE;
#endif /* (defined(CONFIG_MALI450) || defined(CONFIG_MALI470)) */
}

static mali_bool mali_executor_tackle_gp_bound(void)
{
	struct mali_pp_job *job;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	job = mali_scheduler_job_pp_physical_peek();

	if (NULL != job && MALI_TRUE == mali_is_mali400()) {
		if (0 < group_list_working_count &&
		    mali_pp_job_is_large_and_unstarted(job)) {
			return MALI_TRUE;
		}
	}

	return MALI_FALSE;
}

static mali_bool mali_executor_schedule_is_early_out(mali_bool *gpu_secure_mode_is_needed)
{
	struct mali_pp_job *next_pp_job_to_start = NULL;
	struct mali_group *group;
	struct mali_group *tmp_group;
	struct mali_pp_job *physical_pp_job_working = NULL;
	struct mali_pp_job *virtual_pp_job_working = NULL;
	mali_bool gpu_working_in_protected_mode = MALI_FALSE;
	mali_bool gpu_working_in_non_protected_mode = MALI_FALSE;

	MALI_DEBUG_ASSERT_LOCK_HELD(mali_scheduler_lock_obj);

	*gpu_secure_mode_is_needed = MALI_FALSE;

	/* Check if the gpu secure mode is supported, exit if not.*/
	if (MALI_FALSE == _mali_osk_gpu_secure_mode_is_supported()) {
		return MALI_FALSE;
	}

	/* Check if need to set gpu secure mode for the next pp job,
	 * get the next pp job that will be scheduled  if exist.
	 */
	next_pp_job_to_start = mali_scheduler_job_pp_next();

	/* Check current pp physical/virtual running job is protected job or not if exist.*/
	_MALI_OSK_LIST_FOREACHENTRY(group, tmp_group, &group_list_working,
				    struct mali_group, executor_list) {
		physical_pp_job_working = group->pp_running_job;
		break;
	}

	if (EXEC_STATE_WORKING == virtual_group_state) {
		virtual_pp_job_working = virtual_group->pp_running_job;
	}

	if (NULL != physical_pp_job_working) {
		if (MALI_TRUE == mali_pp_job_is_protected_job(physical_pp_job_working)) {
			gpu_working_in_protected_mode = MALI_TRUE;
		} else {
			gpu_working_in_non_protected_mode = MALI_TRUE;
		}
	} else if (NULL != virtual_pp_job_working) {
		if (MALI_TRUE == mali_pp_job_is_protected_job(virtual_pp_job_working)) {
			gpu_working_in_protected_mode = MALI_TRUE;
		} else {
			gpu_working_in_non_protected_mode = MALI_TRUE;
		}
	} else if (EXEC_STATE_WORKING == gp_group_state) {
		gpu_working_in_non_protected_mode = MALI_TRUE;
	}

	/* If the next pp job is the protected pp job.*/
	if ((NULL != next_pp_job_to_start) && MALI_TRUE == mali_pp_job_is_protected_job(next_pp_job_to_start)) {
		/* if gp is working or any non-protected pp job is working now, unable to schedule protected pp job. */
		if (MALI_TRUE == gpu_working_in_non_protected_mode)
			return MALI_TRUE;

		*gpu_secure_mode_is_needed = MALI_TRUE;
		return MALI_FALSE;

	}

	if (MALI_TRUE == gpu_working_in_protected_mode) {
		/* Unable to schedule non-protected pp job/gp job if exist protected pp running jobs*/
		return MALI_TRUE;
	}

	return MALI_FALSE;
}
/*
 * This is where jobs are actually started.
 */
static void mali_executor_schedule(void)
{
	u32 i;
	u32 num_physical_needed = 0;
	u32 num_physical_to_process = 0;
	mali_bool trigger_pm_update = MALI_FALSE;
	mali_bool deactivate_idle_group = MALI_TRUE;
	mali_bool gpu_secure_mode_is_needed = MALI_FALSE;
	mali_bool is_gpu_secure_mode = MALI_FALSE;
	/* Physical groups + jobs to start in this function */
	struct mali_group *groups_to_start[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS];
	struct mali_pp_job *jobs_to_start[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS];
	u32 sub_jobs_to_start[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS];
	int num_jobs_to_start = 0;

	/* Virtual job to start in this function */
	struct mali_pp_job *virtual_job_to_start = NULL;

	/* GP job to start in this function */
	struct mali_gp_job *gp_job_to_start = NULL;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	if (pause_count > 0) {
		/* Execution is suspended, don't schedule any jobs. */
		return;
	}

	/* Lock needed in order to safely handle the job queues */
	mali_scheduler_lock();

	/* 1. Check the schedule if need to early out. */
	if (MALI_TRUE == mali_executor_schedule_is_early_out(&gpu_secure_mode_is_needed)) {
		mali_scheduler_unlock();
		return;
	}

	/* 2. Activate gp firstly if have gp job queued. */
	if ((EXEC_STATE_INACTIVE == gp_group_state)
	    && (0 < mali_scheduler_job_gp_count())
	    && (gpu_secure_mode_is_needed == MALI_FALSE)) {

		enum mali_group_state state =
			mali_group_activate(gp_group);
		if (MALI_GROUP_STATE_ACTIVE == state) {
			/* Set GP group state to idle */
			gp_group_state = EXEC_STATE_IDLE;
		} else {
			trigger_pm_update = MALI_TRUE;
		}
	}

	/* 3. Prepare as many physical groups as needed/possible */

	num_physical_needed = mali_scheduler_job_physical_head_count(gpu_secure_mode_is_needed);

	/* On mali-450 platform, we don't need to enter in this block frequently. */
	if (0 < num_physical_needed) {

		if (num_physical_needed <= group_list_idle_count) {
			/* We have enough groups on idle list already */
			num_physical_to_process = num_physical_needed;
			num_physical_needed = 0;
		} else {
			/* We need to get a hold of some more groups */
			num_physical_to_process = group_list_idle_count;
			num_physical_needed -= group_list_idle_count;
		}

		if (0 < num_physical_needed) {

			/* 3.1. Activate groups which are inactive */

			struct mali_group *group;
			struct mali_group *temp;

			_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_inactive,
						    struct mali_group, executor_list) {
				enum mali_group_state state =
					mali_group_activate(group);
				if (MALI_GROUP_STATE_ACTIVE == state) {
					/* Move from inactive to idle */
					mali_executor_change_state_pp_physical(group,
									       &group_list_inactive,
									       &group_list_inactive_count,
									       &group_list_idle,
									       &group_list_idle_count);
					num_physical_to_process++;
				} else {
					trigger_pm_update = MALI_TRUE;
				}

				num_physical_needed--;
				if (0 == num_physical_needed) {
					/* We have activated all the groups we need */
					break;
				}
			}
		}

		if (mali_executor_virtual_group_is_usable()) {

			/*
			 * 3.2. And finally, steal and activate groups
			 * from virtual group if we need even more
			 */
			while (0 < num_physical_needed) {
				struct mali_group *group;

				group = mali_group_acquire_group(virtual_group);
				if (NULL != group) {
					enum mali_group_state state;

					mali_executor_disable_empty_virtual();

					state = mali_group_activate(group);
					if (MALI_GROUP_STATE_ACTIVE == state) {
						/* Group is ready, add to idle list */
						_mali_osk_list_add(
							&group->executor_list,
							&group_list_idle);
						group_list_idle_count++;
						num_physical_to_process++;
					} else {
						/*
						 * Group is not ready yet,
						 * add to inactive list
						 */
						_mali_osk_list_add(
							&group->executor_list,
							&group_list_inactive);
						group_list_inactive_count++;

						trigger_pm_update = MALI_TRUE;
					}
					num_physical_needed--;
				} else {
					/*
					 * We could not get enough groups
					 * from the virtual group.
					 */
					break;
				}
			}
		}

		/* 3.3. Assign physical jobs to groups */

		if (0 < num_physical_to_process) {
			struct mali_group *group;
			struct mali_group *temp;

			_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_idle,
						    struct mali_group, executor_list) {
				struct mali_pp_job *job = NULL;
				u32 sub_job = MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS;

				MALI_DEBUG_ASSERT(num_jobs_to_start <
						  MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS);

				MALI_DEBUG_ASSERT(0 <
						  mali_scheduler_job_physical_head_count(gpu_secure_mode_is_needed));

				/* If the next pp job is non-protected, check if gp bound now. */
				if ((MALI_FALSE == gpu_secure_mode_is_needed)
				    && (mali_executor_hint_is_enabled(MALI_EXECUTOR_HINT_GP_BOUND))
				    && (MALI_TRUE == mali_executor_tackle_gp_bound())) {
					/*
					* We're gp bound,
					* don't start this right now.
					*/
					deactivate_idle_group = MALI_FALSE;
					num_physical_to_process = 0;
					break;
				}

				job = mali_scheduler_job_pp_physical_get(
					      &sub_job);

				if (MALI_FALSE == gpu_secure_mode_is_needed) {
					MALI_DEBUG_ASSERT(MALI_FALSE == mali_pp_job_is_protected_job(job));
				} else {
					MALI_DEBUG_ASSERT(MALI_TRUE == mali_pp_job_is_protected_job(job));
				}

				MALI_DEBUG_ASSERT_POINTER(job);
				MALI_DEBUG_ASSERT(sub_job <= MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS);

				/* Put job + group on list of jobs to start later on */

				groups_to_start[num_jobs_to_start] = group;
				jobs_to_start[num_jobs_to_start] = job;
				sub_jobs_to_start[num_jobs_to_start] = sub_job;
				num_jobs_to_start++;

				/* Move group from idle to working */
				mali_executor_change_state_pp_physical(group,
								       &group_list_idle,
								       &group_list_idle_count,
								       &group_list_working,
								       &group_list_working_count);

				num_physical_to_process--;
				if (0 == num_physical_to_process) {
					/* Got all we needed */
					break;
				}
			}
		}
	}

	/* 4. Deactivate idle pp group , must put deactive here before active vitual group
	 *    for cover case first only has physical job in normal queue but group inactive,
	 *    so delay the job start go to active group, when group activated,
	 *    call scheduler again, but now if we get high queue virtual job,
	 *    we will do nothing in schedule cause executor schedule stop
	 */

	if (MALI_TRUE == mali_executor_deactivate_list_idle(deactivate_idle_group
			&& (!mali_timeline_has_physical_pp_job()))) {
		trigger_pm_update = MALI_TRUE;
	}

	/* 5. Activate virtual group, if needed */
	if (EXEC_STATE_INACTIVE == virtual_group_state &&
	    MALI_TRUE ==  mali_scheduler_job_next_is_virtual()) {
		struct mali_pp_job *virtual_job = mali_scheduler_job_pp_virtual_peek();
		if ((MALI_FALSE == gpu_secure_mode_is_needed && MALI_FALSE == mali_pp_job_is_protected_job(virtual_job))
		    || (MALI_TRUE == gpu_secure_mode_is_needed && MALI_TRUE == mali_pp_job_is_protected_job(virtual_job))) {
			enum mali_group_state state =
				mali_group_activate(virtual_group);
			if (MALI_GROUP_STATE_ACTIVE == state) {
				/* Set virtual group state to idle */
				virtual_group_state = EXEC_STATE_IDLE;
			} else {
				trigger_pm_update = MALI_TRUE;
			}
		}
	}

	/* 6. To power up group asap,  trigger pm update only when no need to swith the gpu mode. */

	is_gpu_secure_mode = _mali_osk_gpu_secure_mode_is_enabled();

	if ((MALI_FALSE == gpu_secure_mode_is_needed && MALI_FALSE == is_gpu_secure_mode)
	    || (MALI_TRUE == gpu_secure_mode_is_needed && MALI_TRUE == is_gpu_secure_mode)) {
		if (MALI_TRUE == trigger_pm_update) {
			trigger_pm_update = MALI_FALSE;
			mali_pm_update_async();
		}
	}

	/* 7. Assign jobs to idle virtual group (or deactivate if no job) */

	if (EXEC_STATE_IDLE == virtual_group_state) {
		if (MALI_TRUE == mali_scheduler_job_next_is_virtual()) {
			struct mali_pp_job *virtual_job = mali_scheduler_job_pp_virtual_peek();
			if ((MALI_FALSE == gpu_secure_mode_is_needed && MALI_FALSE == mali_pp_job_is_protected_job(virtual_job))
			    || (MALI_TRUE == gpu_secure_mode_is_needed && MALI_TRUE == mali_pp_job_is_protected_job(virtual_job))) {
				virtual_job_to_start =
					mali_scheduler_job_pp_virtual_get();
				virtual_group_state = EXEC_STATE_WORKING;
			}
		} else if (!mali_timeline_has_virtual_pp_job()) {
			virtual_group_state = EXEC_STATE_INACTIVE;

			if (mali_group_deactivate(virtual_group)) {
				trigger_pm_update = MALI_TRUE;
			}
		}
	}

	/* 8. Assign job to idle GP group (or deactivate if no job) */

	if (EXEC_STATE_IDLE == gp_group_state && MALI_FALSE == gpu_secure_mode_is_needed) {
		if (0 < mali_scheduler_job_gp_count()) {
			gp_job_to_start = mali_scheduler_job_gp_get();
			gp_group_state = EXEC_STATE_WORKING;
		} else if (!mali_timeline_has_gp_job()) {
			gp_group_state = EXEC_STATE_INACTIVE;
			if (mali_group_deactivate(gp_group)) {
				trigger_pm_update = MALI_TRUE;
			}
		}
	}

	/* 9. We no longer need the schedule/queue lock */

	mali_scheduler_unlock();

	/* 10. start jobs */
	if (NULL != virtual_job_to_start) {
		MALI_DEBUG_ASSERT(!mali_group_pp_is_active(virtual_group));
		mali_group_start_pp_job(virtual_group,
					virtual_job_to_start, 0, is_gpu_secure_mode);
	}

	for (i = 0; i < num_jobs_to_start; i++) {
		MALI_DEBUG_ASSERT(!mali_group_pp_is_active(
					  groups_to_start[i]));
		mali_group_start_pp_job(groups_to_start[i],
					jobs_to_start[i],
					sub_jobs_to_start[i], is_gpu_secure_mode);
	}

	MALI_DEBUG_ASSERT_POINTER(gp_group);

	if (NULL != gp_job_to_start) {
		MALI_DEBUG_ASSERT(!mali_group_gp_is_active(gp_group));
		mali_group_start_gp_job(gp_group, gp_job_to_start, is_gpu_secure_mode);
	}

	/* 11. Trigger any pending PM updates */
	if (MALI_TRUE == trigger_pm_update) {
		mali_pm_update_async();
	}
}

/* Handler for deferred schedule requests */
static void mali_executor_wq_schedule(void *arg)
{
	MALI_IGNORE(arg);
	mali_executor_lock();
	mali_executor_schedule();
	mali_executor_unlock();
}

static void mali_executor_send_gp_oom_to_user(struct mali_gp_job *job)
{
	_mali_uk_gp_job_suspended_s *jobres;
	_mali_osk_notification_t *notification;

	notification = mali_gp_job_get_oom_notification(job);

	/*
	 * Remember the id we send to user space, so we have something to
	 * verify when we get a response
	 */
	gp_returned_cookie = mali_gp_job_get_id(job);

	jobres = (_mali_uk_gp_job_suspended_s *)notification->result_buffer;
	jobres->user_job_ptr = mali_gp_job_get_user_id(job);
	jobres->cookie = gp_returned_cookie;

	mali_session_send_notification(mali_gp_job_get_session(job),
				       notification);
}
static struct mali_gp_job *mali_executor_complete_gp(struct mali_group *group,
		mali_bool success)
{
	struct mali_gp_job *job;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	/* Extracts the needed HW status from core and reset */
	job = mali_group_complete_gp(group, success);

	MALI_DEBUG_ASSERT_POINTER(job);

	/* Core is now ready to go into idle list */
	gp_group_state = EXEC_STATE_IDLE;

	/* This will potentially queue more GP and PP jobs */
	mali_timeline_tracker_release(&job->tracker);

	/* Signal PP job */
	mali_gp_job_signal_pp_tracker(job, success);

	return job;
}

static struct mali_pp_job *mali_executor_complete_pp(struct mali_group *group,
		mali_bool success)
{
	struct mali_pp_job *job;
	u32 sub_job;
	mali_bool job_is_done;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	/* Extracts the needed HW status from core and reset */
	job = mali_group_complete_pp(group, success, &sub_job);

	MALI_DEBUG_ASSERT_POINTER(job);

	/* Core is now ready to go into idle list */
	if (mali_group_is_virtual(group)) {
		virtual_group_state = EXEC_STATE_IDLE;
	} else {
		/* Move from working to idle state */
		mali_executor_change_state_pp_physical(group,
						       &group_list_working,
						       &group_list_working_count,
						       &group_list_idle,
						       &group_list_idle_count);
	}

	/* It is the executor module which owns the jobs themselves by now */
	mali_pp_job_mark_sub_job_completed(job, success);
	job_is_done = mali_pp_job_is_complete(job);

	if (job_is_done) {
		/* This will potentially queue more GP and PP jobs */
		mali_timeline_tracker_release(&job->tracker);
	}

	return job;
}

static void mali_executor_complete_group(struct mali_group *group,
		mali_bool success,
		struct mali_gp_job **gp_job_done,
		struct mali_pp_job **pp_job_done)
{
	struct mali_gp_core *gp_core = mali_group_get_gp_core(group);
	struct mali_pp_core *pp_core = mali_group_get_pp_core(group);
	struct mali_gp_job *gp_job = NULL;
	struct mali_pp_job *pp_job = NULL;
	mali_bool pp_job_is_done = MALI_TRUE;

	if (NULL != gp_core) {
		gp_job = mali_executor_complete_gp(group, success);
	} else {
		MALI_DEBUG_ASSERT_POINTER(pp_core);
		MALI_IGNORE(pp_core);
		pp_job = mali_executor_complete_pp(group, success);

		pp_job_is_done = mali_pp_job_is_complete(pp_job);
	}

	if (pause_count > 0) {
		/* Execution has been suspended */

		if (!mali_executor_is_working()) {
			/* Last job completed, wake up sleepers */
			_mali_osk_wait_queue_wake_up(
				executor_working_wait_queue);
		}
	} else if (MALI_TRUE == mali_group_disable_requested(group)) {
		mali_executor_core_scale_in_group_complete(group);

		mali_executor_schedule();
	} else {
		/* try to schedule new jobs */
		mali_executor_schedule();
	}

	if (NULL != gp_job) {
		MALI_DEBUG_ASSERT_POINTER(gp_job_done);
		*gp_job_done = gp_job;
	} else if (pp_job_is_done) {
		MALI_DEBUG_ASSERT_POINTER(pp_job);
		MALI_DEBUG_ASSERT_POINTER(pp_job_done);
		*pp_job_done = pp_job;
	}
}

static void mali_executor_change_state_pp_physical(struct mali_group *group,
		_mali_osk_list_t *old_list,
		u32 *old_count,
		_mali_osk_list_t *new_list,
		u32 *new_count)
{
	/*
	 * It's a bit more complicated to change the state for the physical PP
	 * groups since their state is determined by the list they are on.
	 */
#if defined(DEBUG)
	mali_bool found = MALI_FALSE;
	struct mali_group *group_iter;
	struct mali_group *temp;
	u32 old_counted = 0;
	u32 new_counted = 0;

	MALI_DEBUG_ASSERT_POINTER(group);
	MALI_DEBUG_ASSERT_POINTER(old_list);
	MALI_DEBUG_ASSERT_POINTER(old_count);
	MALI_DEBUG_ASSERT_POINTER(new_list);
	MALI_DEBUG_ASSERT_POINTER(new_count);

	/*
	 * Verify that group is present on old list,
	 * and that the count is correct
	 */

	_MALI_OSK_LIST_FOREACHENTRY(group_iter, temp, old_list,
				    struct mali_group, executor_list) {
		old_counted++;
		if (group == group_iter) {
			found = MALI_TRUE;
		}
	}

	_MALI_OSK_LIST_FOREACHENTRY(group_iter, temp, new_list,
				    struct mali_group, executor_list) {
		new_counted++;
	}

	if (MALI_FALSE == found) {
		if (old_list == &group_list_idle) {
			MALI_DEBUG_PRINT(1, (" old Group list is idle,"));
		} else if (old_list == &group_list_inactive) {
			MALI_DEBUG_PRINT(1, (" old Group list is inactive,"));
		} else if (old_list == &group_list_working) {
			MALI_DEBUG_PRINT(1, (" old Group list is working,"));
		} else if (old_list == &group_list_disabled) {
			MALI_DEBUG_PRINT(1, (" old Group list is disable,"));
		}

		if (MALI_TRUE == mali_executor_group_is_in_state(group, EXEC_STATE_WORKING)) {
			MALI_DEBUG_PRINT(1, (" group in working \n"));
		} else if (MALI_TRUE == mali_executor_group_is_in_state(group, EXEC_STATE_INACTIVE)) {
			MALI_DEBUG_PRINT(1, (" group in inactive \n"));
		} else if (MALI_TRUE == mali_executor_group_is_in_state(group, EXEC_STATE_IDLE)) {
			MALI_DEBUG_PRINT(1, (" group in idle \n"));
		} else if (MALI_TRUE == mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED)) {
			MALI_DEBUG_PRINT(1, (" but group in disabled \n"));
		}
	}

	MALI_DEBUG_ASSERT(MALI_TRUE == found);
	MALI_DEBUG_ASSERT(0 < (*old_count));
	MALI_DEBUG_ASSERT((*old_count) == old_counted);
	MALI_DEBUG_ASSERT((*new_count) == new_counted);
#endif

	_mali_osk_list_move(&group->executor_list, new_list);
	(*old_count)--;
	(*new_count)++;
}

static void mali_executor_set_state_pp_physical(struct mali_group *group,
		_mali_osk_list_t *new_list,
		u32 *new_count)
{
	_mali_osk_list_add(&group->executor_list, new_list);
	(*new_count)++;
}

static mali_bool mali_executor_group_is_in_state(struct mali_group *group,
		enum mali_executor_state_t state)
{
	MALI_DEBUG_ASSERT_POINTER(group);
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	if (gp_group == group) {
		if (gp_group_state == state) {
			return MALI_TRUE;
		}
	} else if (virtual_group == group || mali_group_is_in_virtual(group)) {
		if (virtual_group_state == state) {
			return MALI_TRUE;
		}
	} else {
		/* Physical PP group */
		struct mali_group *group_iter;
		struct mali_group *temp;
		_mali_osk_list_t *list;

		if (EXEC_STATE_DISABLED == state) {
			list = &group_list_disabled;
		} else if (EXEC_STATE_INACTIVE == state) {
			list = &group_list_inactive;
		} else if (EXEC_STATE_IDLE == state) {
			list = &group_list_idle;
		} else {
			MALI_DEBUG_ASSERT(EXEC_STATE_WORKING == state);
			list = &group_list_working;
		}

		_MALI_OSK_LIST_FOREACHENTRY(group_iter, temp, list,
					    struct mali_group, executor_list) {
			if (group_iter == group) {
				return MALI_TRUE;
			}
		}
	}

	/* group not in correct state */
	return MALI_FALSE;
}

static void mali_executor_group_enable_internal(struct mali_group *group)
{
	MALI_DEBUG_ASSERT(group);
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED));

	/* Put into inactive state (== "lowest" enabled state) */
	if (group == gp_group) {
		MALI_DEBUG_ASSERT(EXEC_STATE_DISABLED == gp_group_state);
		gp_group_state = EXEC_STATE_INACTIVE;
	} else {
		mali_executor_change_state_pp_physical(group,
						       &group_list_disabled,
						       &group_list_disabled_count,
						       &group_list_inactive,
						       &group_list_inactive_count);

		++num_physical_pp_cores_enabled;
		MALI_DEBUG_PRINT(4, ("Enabling group id %d \n", group->pp_core->core_id));
	}

	if (MALI_GROUP_STATE_ACTIVE == mali_group_activate(group)) {
		MALI_DEBUG_ASSERT(MALI_TRUE == mali_group_power_is_on(group));

		/* Move from inactive to idle */
		if (group == gp_group) {
			gp_group_state = EXEC_STATE_IDLE;
		} else {
			mali_executor_change_state_pp_physical(group,
							       &group_list_inactive,
							       &group_list_inactive_count,
							       &group_list_idle,
							       &group_list_idle_count);

			if (mali_executor_has_virtual_group()) {
				if (mali_executor_physical_rejoin_virtual(group)) {
					mali_pm_update_async();
				}
			}
		}
	} else {
		mali_pm_update_async();
	}
}

static void mali_executor_group_disable_internal(struct mali_group *group)
{
	mali_bool working;

	MALI_DEBUG_ASSERT_POINTER(group);
	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(!mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED));

	working = mali_executor_group_is_in_state(group, EXEC_STATE_WORKING);
	if (MALI_TRUE == working) {
		/** Group to be disabled once it completes current work,
		 * when virtual group completes, also check child groups for this flag */
		mali_group_set_disable_request(group, MALI_TRUE);
		return;
	}

	/* Put into disabled state */
	if (group == gp_group) {
		/* GP group */
		MALI_DEBUG_ASSERT(EXEC_STATE_WORKING != gp_group_state);
		gp_group_state = EXEC_STATE_DISABLED;
	} else {
		if (mali_group_is_in_virtual(group)) {
			/* A child group of virtual group. move the specific group from virtual group */
			MALI_DEBUG_ASSERT(EXEC_STATE_WORKING != virtual_group_state);

			mali_executor_set_state_pp_physical(group,
							    &group_list_disabled,
							    &group_list_disabled_count);

			mali_group_remove_group(virtual_group, group);
			mali_executor_disable_empty_virtual();
		} else {
			mali_executor_change_group_status_disabled(group);
		}

		--num_physical_pp_cores_enabled;
		MALI_DEBUG_PRINT(4, ("Disabling group id %d \n", group->pp_core->core_id));
	}

	if (MALI_GROUP_STATE_INACTIVE != group->state) {
		if (MALI_TRUE == mali_group_deactivate(group)) {
			mali_pm_update_async();
		}
	}
}

static void mali_executor_notify_core_change(u32 num_cores)
{
	mali_bool done = MALI_FALSE;

	if (mali_is_mali450() || mali_is_mali470()) {
		return;
	}

	/*
	 * This function gets a bit complicated because we can't hold the session lock while
	 * allocating notification objects.
	 */
	while (!done) {
		u32 i;
		u32 num_sessions_alloc;
		u32 num_sessions_with_lock;
		u32 used_notification_objects = 0;
		_mali_osk_notification_t **notobjs;

		/* Pre allocate the number of notifications objects we need right now (might change after lock has been taken) */
		num_sessions_alloc = mali_session_get_count();
		if (0 == num_sessions_alloc) {
			/* No sessions to report to */
			return;
		}

		notobjs = (_mali_osk_notification_t **)_mali_osk_malloc(sizeof(_mali_osk_notification_t *) * num_sessions_alloc);
		if (NULL == notobjs) {
			MALI_PRINT_ERROR(("Failed to notify user space session about num PP core change (alloc failure)\n"));
			/* there is probably no point in trying again, system must be really low on memory and probably unusable now anyway */
			return;
		}

		for (i = 0; i < num_sessions_alloc; i++) {
			notobjs[i] = _mali_osk_notification_create(_MALI_NOTIFICATION_PP_NUM_CORE_CHANGE, sizeof(_mali_uk_pp_num_cores_changed_s));
			if (NULL != notobjs[i]) {
				_mali_uk_pp_num_cores_changed_s *data = notobjs[i]->result_buffer;
				data->number_of_enabled_cores = num_cores;
			} else {
				MALI_PRINT_ERROR(("Failed to notify user space session about num PP core change (alloc failure %u)\n", i));
			}
		}

		mali_session_lock();

		/* number of sessions will not change while we hold the lock */
		num_sessions_with_lock = mali_session_get_count();

		if (num_sessions_alloc >= num_sessions_with_lock) {
			/* We have allocated enough notification objects for all the sessions atm */
			struct mali_session_data *session, *tmp;
			MALI_SESSION_FOREACH(session, tmp, link) {
				MALI_DEBUG_ASSERT(used_notification_objects < num_sessions_alloc);
				if (NULL != notobjs[used_notification_objects]) {
					mali_session_send_notification(session, notobjs[used_notification_objects]);
					notobjs[used_notification_objects] = NULL; /* Don't track this notification object any more */
				}
				used_notification_objects++;
			}
			done = MALI_TRUE;
		}

		mali_session_unlock();

		/* Delete any remaining/unused notification objects */
		for (; used_notification_objects < num_sessions_alloc; used_notification_objects++) {
			if (NULL != notobjs[used_notification_objects]) {
				_mali_osk_notification_delete(notobjs[used_notification_objects]);
			}
		}

		_mali_osk_free(notobjs);
	}
}

static mali_bool mali_executor_core_scaling_is_done(void *data)
{
	u32 i;
	u32 num_groups;
	mali_bool ret = MALI_TRUE;

	MALI_IGNORE(data);

	mali_executor_lock();

	num_groups = mali_group_get_glob_num_groups();

	for (i = 0; i < num_groups; i++) {
		struct mali_group *group = mali_group_get_glob_group(i);

		if (NULL != group) {
			if (MALI_TRUE == group->disable_requested && NULL != mali_group_get_pp_core(group)) {
				ret = MALI_FALSE;
				break;
			}
		}
	}
	mali_executor_unlock();

	return ret;
}

static void mali_executor_wq_notify_core_change(void *arg)
{
	MALI_IGNORE(arg);

	if (mali_is_mali450() || mali_is_mali470()) {
		return;
	}

	_mali_osk_wait_queue_wait_event(executor_notify_core_change_wait_queue,
					mali_executor_core_scaling_is_done, NULL);

	mali_executor_notify_core_change(num_physical_pp_cores_enabled);
}

/**
 * Clear all disable request from the _last_ core scaling behavior.
 */
static void mali_executor_core_scaling_reset(void)
{
	u32 i;
	u32 num_groups;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	num_groups = mali_group_get_glob_num_groups();

	for (i = 0; i < num_groups; i++) {
		struct mali_group *group = mali_group_get_glob_group(i);

		if (NULL != group) {
			group->disable_requested = MALI_FALSE;
		}
	}

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		core_scaling_delay_up_mask[i] = 0;
	}
}

static void mali_executor_core_scale(unsigned int target_core_nr)
{
	int current_core_scaling_mask[MALI_MAX_NUMBER_OF_DOMAINS] = { 0 };
	int target_core_scaling_mask[MALI_MAX_NUMBER_OF_DOMAINS] = { 0 };
	int i;

	MALI_DEBUG_ASSERT(0 < target_core_nr);
	MALI_DEBUG_ASSERT(num_physical_pp_cores_total >= target_core_nr);

	mali_executor_lock();

	if (target_core_nr < num_physical_pp_cores_enabled) {
		MALI_DEBUG_PRINT(2, ("Requesting %d cores: disabling %d cores\n", target_core_nr, num_physical_pp_cores_enabled - target_core_nr));
	} else {
		MALI_DEBUG_PRINT(2, ("Requesting %d cores: enabling %d cores\n", target_core_nr, target_core_nr - num_physical_pp_cores_enabled));
	}

	/* When a new core scaling request is comming,  we should remove the un-doing
	 * part of the last core scaling request.  It's safe because we have only one
	 * lock(executor lock) protection. */
	mali_executor_core_scaling_reset();

	mali_pm_get_best_power_cost_mask(num_physical_pp_cores_enabled, current_core_scaling_mask);
	mali_pm_get_best_power_cost_mask(target_core_nr, target_core_scaling_mask);

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		target_core_scaling_mask[i] = target_core_scaling_mask[i] - current_core_scaling_mask[i];
		MALI_DEBUG_PRINT(5, ("target_core_scaling_mask[%d] = %d\n", i, target_core_scaling_mask[i]));
	}

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		if (0 > target_core_scaling_mask[i]) {
			struct mali_pm_domain *domain;

			domain = mali_pm_domain_get_from_index(i);

			/* Domain is valid and has pp cores */
			if ((NULL != domain) && !(_mali_osk_list_empty(&domain->group_list))) {
				struct mali_group *group;
				struct mali_group *temp;

				_MALI_OSK_LIST_FOREACHENTRY(group, temp, &domain->group_list, struct mali_group, pm_domain_list) {
					if (NULL != mali_group_get_pp_core(group) && (!mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED))
					    && (!mali_group_is_virtual(group))) {
						mali_executor_group_disable_internal(group);
						target_core_scaling_mask[i]++;
						if ((0 == target_core_scaling_mask[i])) {
							break;
						}

					}
				}
			}
		}
	}

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		/**
		 * Target_core_scaling_mask[i] is bigger than 0,
		 * means we need to enable some pp cores in
		 * this domain whose domain index is i.
		 */
		if (0 < target_core_scaling_mask[i]) {
			struct mali_pm_domain *domain;

			if (num_physical_pp_cores_enabled >= target_core_nr) {
				break;
			}

			domain = mali_pm_domain_get_from_index(i);

			/* Domain is valid and has pp cores */
			if ((NULL != domain) && !(_mali_osk_list_empty(&domain->group_list))) {
				struct mali_group *group;
				struct mali_group *temp;

				_MALI_OSK_LIST_FOREACHENTRY(group, temp, &domain->group_list, struct mali_group, pm_domain_list) {
					if (NULL != mali_group_get_pp_core(group) && mali_executor_group_is_in_state(group, EXEC_STATE_DISABLED)
					    && (!mali_group_is_virtual(group))) {
						mali_executor_group_enable_internal(group);
						target_core_scaling_mask[i]--;

						if ((0 == target_core_scaling_mask[i]) || num_physical_pp_cores_enabled == target_core_nr) {
							break;
						}
					}
				}
			}
		}
	}

	/**
	 * Here, we may still have some pp cores not been enabled because of some
	 * pp cores need to be disabled are still in working state.
	 */
	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		if (0 < target_core_scaling_mask[i]) {
			core_scaling_delay_up_mask[i] = target_core_scaling_mask[i];
		}
	}

	mali_executor_schedule();
	mali_executor_unlock();
}

static void mali_executor_core_scale_in_group_complete(struct mali_group *group)
{
	int num_pp_cores_disabled = 0;
	int num_pp_cores_to_enable = 0;
	int i;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();
	MALI_DEBUG_ASSERT(MALI_TRUE == mali_group_disable_requested(group));

	/* Disable child group of virtual group */
	if (mali_group_is_virtual(group)) {
		struct mali_group *child;
		struct mali_group *temp;

		_MALI_OSK_LIST_FOREACHENTRY(child, temp, &group->group_list, struct mali_group, group_list) {
			if (MALI_TRUE == mali_group_disable_requested(child)) {
				mali_group_set_disable_request(child, MALI_FALSE);
				mali_executor_group_disable_internal(child);
				num_pp_cores_disabled++;
			}
		}
		mali_group_set_disable_request(group, MALI_FALSE);
	} else {
		mali_executor_group_disable_internal(group);
		mali_group_set_disable_request(group, MALI_FALSE);
		if (NULL != mali_group_get_pp_core(group)) {
			num_pp_cores_disabled++;
		}
	}

	num_pp_cores_to_enable = num_pp_cores_disabled;

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		if (0 < core_scaling_delay_up_mask[i]) {
			struct mali_pm_domain *domain;

			if (0 == num_pp_cores_to_enable) {
				break;
			}

			domain = mali_pm_domain_get_from_index(i);

			/* Domain is valid and has pp cores */
			if ((NULL != domain) && !(_mali_osk_list_empty(&domain->group_list))) {
				struct mali_group *disabled_group;
				struct mali_group *temp;

				_MALI_OSK_LIST_FOREACHENTRY(disabled_group, temp, &domain->group_list, struct mali_group, pm_domain_list) {
					if (NULL != mali_group_get_pp_core(disabled_group) && mali_executor_group_is_in_state(disabled_group, EXEC_STATE_DISABLED)) {
						mali_executor_group_enable_internal(disabled_group);
						core_scaling_delay_up_mask[i]--;
						num_pp_cores_to_enable--;

						if ((0 == core_scaling_delay_up_mask[i]) || 0 == num_pp_cores_to_enable) {
							break;
						}
					}
				}
			}
		}
	}

	_mali_osk_wait_queue_wake_up(executor_notify_core_change_wait_queue);
}

static void mali_executor_change_group_status_disabled(struct mali_group *group)
{
	/* Physical PP group */
	mali_bool idle;

	MALI_DEBUG_ASSERT_EXECUTOR_LOCK_HELD();

	idle = mali_executor_group_is_in_state(group, EXEC_STATE_IDLE);
	if (MALI_TRUE == idle) {
		mali_executor_change_state_pp_physical(group,
						       &group_list_idle,
						       &group_list_idle_count,
						       &group_list_disabled,
						       &group_list_disabled_count);
	} else {
		mali_executor_change_state_pp_physical(group,
						       &group_list_inactive,
						       &group_list_inactive_count,
						       &group_list_disabled,
						       &group_list_disabled_count);
	}
}

static mali_bool mali_executor_deactivate_list_idle(mali_bool deactivate_idle_group)
{
	mali_bool trigger_pm_update = MALI_FALSE;

	if (group_list_idle_count > 0) {
		if (mali_executor_has_virtual_group()) {

			/* Rejoin virtual group on Mali-450 */

			struct mali_group *group;
			struct mali_group *temp;

			_MALI_OSK_LIST_FOREACHENTRY(group, temp,
						    &group_list_idle,
						    struct mali_group, executor_list) {
				if (mali_executor_physical_rejoin_virtual(
					    group)) {
					trigger_pm_update = MALI_TRUE;
				}
			}
		} else if (deactivate_idle_group) {
			struct mali_group *group;
			struct mali_group *temp;

			/* Deactivate group on Mali-300/400 */

			_MALI_OSK_LIST_FOREACHENTRY(group, temp,
						    &group_list_idle,
						    struct mali_group, executor_list) {
				if (mali_group_deactivate(group)) {
					trigger_pm_update = MALI_TRUE;
				}

				/* Move from idle to inactive */
				mali_executor_change_state_pp_physical(group,
								       &group_list_idle,
								       &group_list_idle_count,
								       &group_list_inactive,
								       &group_list_inactive_count);
			}
		}
	}

	return trigger_pm_update;
}

void mali_executor_running_status_print(void)
{
	struct mali_group *group = NULL;
	struct mali_group *temp = NULL;

	MALI_PRINT(("GP running job: %p\n", gp_group->gp_running_job));
	if ((gp_group->gp_core) && (gp_group->is_working)) {
		mali_group_dump_status(gp_group);
	}
	MALI_PRINT(("Physical PP groups in WORKING state (count = %u):\n", group_list_working_count));
	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_working, struct mali_group, executor_list) {
		MALI_PRINT(("PP running job: %p, subjob %d \n", group->pp_running_job, group->pp_running_sub_job));
		mali_group_dump_status(group);
	}
	MALI_PRINT(("Physical PP groups in INACTIVE state (count = %u):\n", group_list_inactive_count));
	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_inactive, struct mali_group, executor_list) {
		MALI_PRINT(("\tPP status %d, SW power: %s\n", group->state, group->power_is_on ? "On" : "Off"));
		MALI_PRINT(("\tPP #%d: %s\n", group->pp_core->core_id, group->pp_core->hw_core.description));
	}
	MALI_PRINT(("Physical PP groups in IDLE state (count = %u):\n", group_list_idle_count));
	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_idle, struct mali_group, executor_list) {
		MALI_PRINT(("\tPP status %d, SW power: %s\n", group->state, group->power_is_on ? "On" : "Off"));
		MALI_PRINT(("\tPP #%d: %s\n", group->pp_core->core_id, group->pp_core->hw_core.description));
	}
	MALI_PRINT(("Physical PP groups in DISABLED state (count = %u):\n", group_list_disabled_count));
	_MALI_OSK_LIST_FOREACHENTRY(group, temp, &group_list_disabled, struct mali_group, executor_list) {
		MALI_PRINT(("\tPP status %d, SW power: %s\n", group->state, group->power_is_on ? "On" : "Off"));
		MALI_PRINT(("\tPP #%d: %s\n", group->pp_core->core_id, group->pp_core->hw_core.description));
	}

	if (mali_executor_has_virtual_group()) {
		MALI_PRINT(("Virtual group running job: %p\n", virtual_group->pp_running_job));
		MALI_PRINT(("Virtual group status: %d\n", virtual_group_state));
		MALI_PRINT(("Virtual group->status: %d\n", virtual_group->state));
		MALI_PRINT(("\tSW power: %s\n", virtual_group->power_is_on ? "On" : "Off"));
		_MALI_OSK_LIST_FOREACHENTRY(group, temp, &virtual_group->group_list,
					    struct mali_group, group_list) {
			int i = 0;
			MALI_PRINT(("\tchild group(%s) running job: %p\n", group->pp_core->hw_core.description, group->pp_running_job));
			MALI_PRINT(("\tchild group(%s)->status: %d\n", group->pp_core->hw_core.description, group->state));
			MALI_PRINT(("\tchild group(%s) SW power: %s\n", group->pp_core->hw_core.description, group->power_is_on ? "On" : "Off"));
			if (group->pm_domain) {
				MALI_PRINT(("\tPower domain: id %u\n", mali_pm_domain_get_id(group->pm_domain)));
				MALI_PRINT(("\tMask:0x%04x \n", mali_pm_domain_get_mask(group->pm_domain)));
				MALI_PRINT(("\tUse-count:%u \n", mali_pm_domain_get_use_count(group->pm_domain)));
				MALI_PRINT(("\tCurrent power status:%s \n", (mali_pm_domain_get_mask(group->pm_domain)& mali_pm_get_current_mask()) ? "On" : "Off"));
				MALI_PRINT(("\tWanted  power status:%s \n", (mali_pm_domain_get_mask(group->pm_domain)& mali_pm_get_wanted_mask()) ? "On" : "Off"));
			}

			for (i = 0; i < 2; i++) {
				if (NULL != group->l2_cache_core[i]) {
					struct mali_pm_domain *domain;
					domain = mali_l2_cache_get_pm_domain(group->l2_cache_core[i]);
					MALI_PRINT(("\t L2(index %d) group SW power: %s\n", i, group->l2_cache_core[i]->power_is_on ? "On" : "Off"));
					if (domain) {
						MALI_PRINT(("\tL2 Power domain: id %u\n", mali_pm_domain_get_id(domain)));
						MALI_PRINT(("\tL2 Mask:0x%04x \n", mali_pm_domain_get_mask(domain)));
						MALI_PRINT(("\tL2 Use-count:%u \n", mali_pm_domain_get_use_count(domain)));
						MALI_PRINT(("\tL2 Current power status:%s \n", (mali_pm_domain_get_mask(domain) & mali_pm_get_current_mask()) ? "On" : "Off"));
						MALI_PRINT(("\tL2 Wanted  power status:%s \n", (mali_pm_domain_get_mask(domain) & mali_pm_get_wanted_mask()) ? "On" : "Off"));
					}
				}
			}
		}
		if (EXEC_STATE_WORKING == virtual_group_state) {
			mali_group_dump_status(virtual_group);
		}
	}
}

void mali_executor_status_dump(void)
{
	mali_executor_lock();
	mali_scheduler_lock();

	/* print schedule queue status */
	mali_scheduler_gp_pp_job_queue_print();

	mali_scheduler_unlock();
	mali_executor_unlock();
}
